﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  [Serializable]
  public class OrderedDictionary<TKey, TValue> : ItemDictionary<TKey, TValue>, IOrderedDictionary<TKey, TValue>, IReadOnlyOrderedDictionary<TKey, TValue>
  {
    [DebuggerStepThrough]
    public OrderedDictionary() : this(0) { }

    [DebuggerStepThrough]
    public OrderedDictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { }

    [DebuggerStepThrough]
    public OrderedDictionary(int capacity, IEqualityComparer<TKey> comparer = null)
      : base(capacity, comparer) {
      KeysList = new List<TKey>(capacity);
      ValuesList = new List<TValue>(capacity);
      KeysListReadOnly = new ReadOnlyCollection<TKey>(KeysList);
      ValuesListReadOnly = new ReadOnlyCollection<TValue>(ValuesList);
    }

    private IList<TKey> KeysList { get; set; }
    private IList<TValue> ValuesList { get; set; }

    private ReadOnlyCollection<TKey> KeysListReadOnly { get; set; }
    private ReadOnlyCollection<TValue> ValuesListReadOnly { get; set; }

    public IEqualityComparer<TKey> Comparer {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<IEqualityComparer<TKey>>() != null);
        var dictionary = (Dictionary<TKey, TValue>)Dictionary;
        return dictionary.Comparer;
      }
    }

    [ContractInvariantMethod]
    private void Invariant() {
      Contract.Invariant(Dictionary is Dictionary<TKey, TValue>);
      Contract.Invariant(KeysList != null);
      Contract.Invariant(ValuesList != null);
      Contract.Invariant(KeysListReadOnly != null);
      Contract.Invariant(ValuesListReadOnly != null);
      Contract.Invariant(KeysList.Count == Count);
      Contract.Invariant(ValuesList.Count == Count);
      Contract.Invariant(KeysListReadOnly.Count == Count);
      Contract.Invariant(ValuesListReadOnly.Count == Count);
    }

    protected virtual KeyValuePair<TKey, TValue> GetItem(int index) {
      Argument.OutOfRange(index >= 0, "index", "index >= 0");
      Argument.OutOfRange(index < Count, "index", "index < Count");

      var key = KeysList[index];
      var value = ValuesList[index];
      return Pair.Create(key, value);
    }

    [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = Justifications.ByDesign)]
    protected virtual void AddItem(TKey key, TValue value, out int index) {
      Contract.Ensures(Contract.ValueAtReturn(out index) >= 0);
      Contract.Ensures(Contract.ValueAtReturn(out index) < Count);
      Contract.Assume(Count > 0);

      KeysList.Add(key);
      ValuesList.Add(value);
      index = Count - 1;
    }

    protected virtual void SetItem(int index, TKey key, TValue value) {
      Argument.OutOfRange(index >= 0, "index", "index >= 0");
      Argument.OutOfRange(index < Count, "index", "index < Count");

      KeysList[index] = key;
      ValuesList[index] = value;
    }

    protected virtual void RemoveItem(int index, TKey key) {
      Argument.OutOfRange(index >= 0, "index", "index >= 0");
      Argument.OutOfRange(index < Count, "index", "index < Count");

      KeysList.RemoveAt(index);
      ValuesList.RemoveAt(index);
      Contract.Assume(Dictionary.Count == Count);
    }

    public int FindIndex(TKey key) {
      Contract.Ensures(Contract.Result<int>() >= -1);
      Contract.Ensures(Contract.Result<int>() < Count);

      return Dictionary.ContainsKey(key) ? FindIndexCore(key) : -1;
    }

    private int FindIndexCore(TKey key) {
      Contract.Ensures(Contract.Result<int>() >= 0);
      Contract.Ensures(Contract.Result<int>() < Count);

      for(var index = 0; index < KeysList.Count; index++) {
        var item = KeysList[index];
        if(Comparer.Equals(item, key)) {
          return index;
        }//if
      }//for

      throw ExceptionBuilder.KeyNotFound(key);
    }

    public ReadOnlyOrderedDictionary<TKey, TValue> AsReadOnly() {
      return new ReadOnlyOrderedDictionary<TKey, TValue>(this);
    }

    protected override ICollection<TKey> KeyItems {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TKey>>() != null);
        Contract.Ensures(Contract.Result<ICollection<TKey>>().Count == Count);
        return KeysListReadOnly;
      }
    }

    protected override ICollection<TValue> ValueItems {
      [DebuggerStepThrough]
      get {
        Contract.Ensures(Contract.Result<ICollection<TValue>>() != null);
        Contract.Ensures(Contract.Result<ICollection<TValue>>().Count == Count);
        return ValuesListReadOnly;
      }
    }

    protected sealed override void AddItem(TKey key, TValue value) {
      base.AddItem(key, value);
      int index;
      AddItem(key, value, out index);
    }

    protected sealed override void SetItem(TKey key, TValue value) {
      if(Dictionary.ContainsKey(key)) {
        base.SetItem(key, value);
        var index = FindIndexCore(key);
        SetItem(index, key, value);
      } else {
        Add(key, value);
      }//if
    }

    protected sealed override bool RemoveItem(TKey key) {
      if(base.RemoveItem(key)) {
        var index = FindIndexCore(key);
        RemoveItem(index, key);
        return true;
      }//if

      return false;
    }

    protected override void ClearItems() {
      Contract.Ensures(Count == 0);
      base.ClearItems();
      KeysList.Clear();
      ValuesList.Clear();
      Contract.Assume(Dictionary.Count == Count);
    }

    protected override void CopyItemsTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
      Argument.NotNull(array, "array");
      Argument.OutOfRange(arrayIndex >= 0, "arrayIndex");
      Argument.OutOfRange(arrayIndex <= array.Length - Count, "arrayIndex");

      for(var index = 0; index < Count; index++) {
        array[arrayIndex + index] = GetItem(index);
      }//for
    }

    protected override IEnumerator<KeyValuePair<TKey, TValue>> GetItemEnumerator() {
      Contract.Ensures(Contract.Result<IEnumerator<KeyValuePair<TKey, TValue>>>() != null);
      for(var index = 0; index < Count; index++) {
        yield return GetItem(index);
      }//for
    }

    #region IOrderedDictionary<TKey, TValue> Members

    public new IReadOnlyList<TKey> Keys {
      [DebuggerStepThrough]
      get { return KeysListReadOnly; }
    }

    public new IReadOnlyList<TValue> Values {
      [DebuggerStepThrough]
      get { return ValuesListReadOnly; }
    }

    public int IndexOf(TKey key) {
      return FindIndex(key);
    }

    public void Insert(int index, TKey key, TValue value) {
      Argument.OutOfRange(index >= 0, "index", "index >= 0");
      Argument.OutOfRange(index <= Count, "index", "index <= Count");

      Dictionary.Add(key, value);
      KeysList.Insert(index, key);
      ValuesList.Insert(index, value);
      Contract.Assume(Dictionary.Count == Count);
    }

    #endregion IOrderedDictionary<TKey, TValue> Members

    #region IList<KeyValuePair<TKey, TValue>> Members

    public KeyValuePair<TKey, TValue> this[int index] {
      [DebuggerStepThrough]
      get {
        Argument.OutOfRange(index >= 0, "index", "index >= 0");
        Argument.OutOfRange(index < Count, "index", "index < Count");
        return GetItem(index);
      }

      set {
        Argument.OutOfRange(index >= 0, "index", "index >= 0");
        Argument.OutOfRange(index < Count, "index", "index < Count");

        var key = KeysList[index];
        if(Comparer.Equals(value.Key, key)) { // Can update only "value".
          Dictionary[key] = value.Value;
          ValuesList[index] = value.Value;
        } else { // Update "key" and "value".
          var removed = Dictionary.Remove(key);
          Contract.Assume(removed);

          IDictionary<TKey, TValue> dictionary = Dictionary;
          dictionary.Add(value);

          KeysList[index] = value.Key;
          ValuesList[index] = value.Value;
        }//if
      }
    }

    public int IndexOf(KeyValuePair<TKey, TValue> item) {
      return IndexOf(item.Key);
    }

    public void Insert(int index, KeyValuePair<TKey, TValue> item) {
      Insert(index, item.Key, item.Value);
    }

    [SuppressMessage("Microsoft.Contracts", "Ensures", Justification = Justifications.CodeContractsIssue)]
    public void RemoveAt(int index) {
      Argument.OutOfRange(index >= 0, "index", "index >= 0");
      Argument.OutOfRange(index < Count, "index", "index < Count");
      Contract.Ensures(Count == Contract.OldValue(Count) - 1);

      var key = KeysList[index];
      var removed = Dictionary.Remove(key);
      Contract.Assume(removed);

      KeysList.RemoveAt(index);
      ValuesList.RemoveAt(index);
      Contract.Assume(Dictionary.Count == Count);
    }

    #endregion IList<KeyValuePair<TKey, TValue>> Members

    #region IReadOnlyList<KeyValuePair<TKey, TValue>> Members

    KeyValuePair<TKey, TValue> IReadOnlyList<KeyValuePair<TKey, TValue>>.this[int index] {
      [DebuggerStepThrough]
      get { return this[index]; }
    }

    #endregion IReadOnlyList<KeyValuePair<TKey, TValue>> Members
  }
}
